/*
  Copyright (c) 2009 Ullio
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:
  
  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.
  
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
*/


// Ye Olde Gyde to Videoe Display

// http://flexissues.blogspot.com/
// http://www.nayansavla.com/blog/?p=13
// http://www.jlacivita.com/2007/11/14/race-conditions-in-flex-videodisplay/
// http://saturnboy.com/2009/05/degrafa-video-player-1/
// http://www.boostworthy.com/blog/?p=274#comments
// http://themathiseasy.com/blog/2009/04/videodisplay-improvements/
// http://www.fxcomponents.com/item/flex-video-player/29/
// http://www.riacodes.com/flex/basic-video-player-in-flex/
// http://alainthibodeau.com/blog/?p=65
// http://blog.flexexamples.com/2007/08/02/scrubbing-an-flv-using-the-videodisplay-control-and-a-slider/
// http://www.osflv.com/
// http://www.openvideoplayer.com/
// http://www.rockonflash.com/red5/demos/FLVPlayer/srcview/index.html
// http://code.google.com/p/flex-tube/source/browse/trunk/com/fs/FSVideoDisplay.as?r=8
// http://richapps.de/?page_id=120
// http://netromedia.com/Support/Forum/tabid/58/view/topic/postid/404/language/en-US/Default.aspx


package ullio.components
{
	import flash.display.StageDisplayState;
	import flash.events.Event;
	import flash.events.FullScreenEvent;
	import flash.events.ProgressEvent;
	
	import mx.controls.Label;
	import mx.controls.VideoDisplay;
	import mx.controls.videoClasses.VideoError;
	import mx.core.mx_internal;
	import mx.events.CuePointEvent;
	import mx.events.MetadataEvent;
	import mx.events.VideoEvent;
	import mx.formatters.DateFormatter;
	import mx.formatters.NumberBaseRoundType;
	import mx.formatters.NumberFormatter;
	import mx.utils.ObjectUtil;
	
	import ullio.events.AppEvent;
	import ullio.video.VideoControlBar;

	use namespace mx_internal;
	
	// STYLES //	
	[Style(name="timerFontSize", type="Number", inherit="no")]

	// EVENTS //
	[Event(name="smoothingChanged", type="flash.events.Event")]
	
	
	
	public class FlexVideoPlayer extends VideoDisplay
	{
		/**
	     *  Constructor.
	     */
		public function FlexVideoPlayer()
		{
			super();
			super.autoPlay = false;	// autoplay is evil		
			_numFormatter.precision = 2;
			_numFormatter.rounding = NumberBaseRoundType.NONE;
			_dateFormatter.formatString = 'NN:SS';	
		}
		
		// LIFE CYCLE //
		override protected function createChildren():void
		{
			super.createChildren();			
			_playerX = this.x;
			_playerY = this.y;
			
			_statusLabel = new Label();
			_statusLabel.selectable = false;			
			addChild(_statusLabel);
			
			_videoControlBar = new VideoControlBar();
			_videoControlBar.height = _controlBarHeight;
			_videoControlBar.width = _controlBarWidth > 0 ? _controlBarWidth : width; 
			addChild(_videoControlBar);
			
			_videoControlBar.timerLabel.text = 'Loading';
			
			if (systemManager.stage != null && !systemManager.stage.hasEventListener(FullScreenEvent.FULL_SCREEN))
				systemManager.stage.addEventListener(FullScreenEvent.FULL_SCREEN, fullScreenHandler);
							
			addEventListener("bufferTimeChanged", onBufferTimeChanged);
			addEventListener("autoRewindChanged", onAutoRewindChanged);
			addEventListener("sourceChanged", onSourceChanged);
			addEventListener("autoBandWidthDetectionChanged", onAutoBandWidthDetectionChanged);
			addEventListener(VideoEvent.STATE_CHANGE, onStateChange);
			addEventListener(VideoEvent.PLAYHEAD_UPDATE, onPlayheadUpdate);
			addEventListener(VideoEvent.READY, onVideoReady);
			addEventListener(VideoEvent.REWIND, onVideoRewind);
			addEventListener(VideoEvent.COMPLETE, onVideoComplete);
			addEventListener(VideoEvent.CLOSE, onVideoClose);
			addEventListener(ProgressEvent.PROGRESS, onProgress);
			addEventListener(MetadataEvent.METADATA_RECEIVED, onMetadataReceived);
			addEventListener(CuePointEvent.CUE_POINT, onCuePoint);
			
			addEventListener("fullScreenClick", onFullScreenClick);
			addEventListener("playPauseClick", onPlayPauseClick);
			addEventListener("scrubberClick", onScrubberClick);
			addEventListener("scrubberMouseDown", onScrubberMouseDown);
			addEventListener("volumeChange", onVolumeChange);			
		}
		override protected function commitProperties():void
		{
			super.commitProperties();
			
			if (_smoothingChanged)
            {
          		smoothVideo();
            }
			_statusLabel.styleName = 'statusText';
			
			_timerFontSize = getStyle("timerFontSize");
       		if(!_timerFontSize)
        		_timerFontSize = 9;
		}
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    	{
			super.updateDisplayList(unscaledWidth, unscaledHeight);
			if (_timerFontSize > 0)
				_videoControlBar.timerLabel.setStyle("fontSize",_timerFontSize);				
			
			_statusLabel.height = 45;
			_statusLabel.width = 125;
			_statusLabel.x = 10;
			_statusLabel.y = 5;
				
			_videoControlBar.y = unscaledHeight - _videoControlBar.height;			
			_videoControlBar.ppButton.enabled = (source == null || source == '') ? false : true;	
			_videoControlBar.volumeSlider.value = volume;
		}
		
		
		
		    	
    	// EVENTS DISPATCHED FROM CONTROL BAR
    	private function onScrubberMouseDown(event:AppEvent):void{
    		trace("scrubber mouse down: " + playheadTime);
    		if (hasEventListener(VideoEvent.PLAYHEAD_UPDATE))
    			removeEventListener(VideoEvent.PLAYHEAD_UPDATE,onPlayheadUpdate);
    	}
    	private function onFullScreenClick(event:AppEvent):void{
    		fullScreen = !_fullScreen;
    	}
    	private function onPlayPauseClick(event:AppEvent):void{
    		event['selected'] ? play() : pause(); 
    	}
    	private function onScrubberClick(event:AppEvent):void{
    		trace("scrubber clicked: " + event['time']);	
    		playheadTime = event['time'];
    	}
    	private function onVolumeChange(event:AppEvent):void{
    		volume = event['volume'];
    	}
    	
    	
    	
    	
    	// EVENTS DISPATCHED FROM VIDEO DISPLAY
    	private function onBufferTimeChanged(event:Event):void{
    		trace("onBufferTimeChanged bufferTime is: " + bufferTime);
    		if (_debug)
    			traceVideo += "onBufferTimeChanged bufferTime is: " + bufferTime.toString() + "\n";
    	}
    	private function onAutoRewindChanged(event:Event):void{
    		trace("auto rewind changed");
    		if (_debug)
    			traceVideo += "onAutoRewindChanged\n";
    	}
    	private function onAutoBandWidthDetectionChanged(event:Event):void{
    		trace("auto bandwidth detection changed");
    		if (_debug)
    			traceVideo += "onAutoBandWidthDetectionChanged\n";
    	}
    	private function onSourceChanged(event:Event):void{
    		trace("source has changed");
    		if (_debug)
    			traceVideo += "onSourceChanged\n";
    			
    		tryPlay();
    	}
    	
    	private function onVideoRewind(event:VideoEvent):void{
    		trace("video has rewound"); 
    		if (_debug)
    			traceVideo += "onVideoRewind\n";	   		
    	}
    	private function onStateChange(event:VideoEvent):void{
    		trace("onStateChange: " + event.state + " and stateResponsive: " + event.stateResponsive + " and playheadTime: " + playheadTime);
    		    		
    		if (_debug)
    			traceVideo += "onStateChange: " + event.state + " stateResponsive: " + event.stateResponsive + "\n";
    		
    		_statusLabel.text = '';
    		
    		if (event.state == VideoEvent.PLAYING && event.stateResponsive){    			
				_videoControlBar.ppButton.selected = true;
    		}else if (event.state == VideoEvent.PAUSED && event.stateResponsive){
    			_videoControlBar.ppButton.selected = false;
    		}else if (event.state == VideoEvent.STOPPED && event.stateResponsive){
    			_videoControlBar.ppButton.selected = false;
    		}else if (event.state == VideoEvent.SEEKING){
    			_statusLabel.text = 'Seeking...';
    		}else if (event.state == VideoEvent.BUFFERING){
    			_statusLabel.text = 'Buffering...';    			
    		}else if (event.state == VideoEvent.DISCONNECTED){    			
    			_statusLabel.text = 'Disconnected...'; 
    			_videoControlBar.timerLabel.text = "CONN ERROR";
    		}else if (event.state == VideoEvent.CONNECTION_ERROR){
    			_videoControlBar.timerLabel.text = _playerHasLoaded ? "CONN ERROR" : "SERVER ERROR";
    			_videoControlBar.ppButton.enabled = false;    			
    			// if the video was on a non-existant or invalid server, you can't close the connection
    			if (_playerHasLoaded){
    				close();
    				clear();
    			}
    		}
    		
    		// ensures there's no wacky scrubber jumping
    		if (stateResponsive)
    			if (!hasEventListener(VideoEvent.PLAYHEAD_UPDATE))
					addEventListener(VideoEvent.PLAYHEAD_UPDATE, onPlayheadUpdate);
    		
    	}
    	private function onPlayheadUpdate(event:VideoEvent):void
		{
    		//trace("onPlayheadUpdate: " + event.playheadTime);
    		if (_debug)
    			traceVideo += "onPlayheadUpdate: " + event.playheadTime + "\n";
    		
    		_videoControlBar.scrubberSlider.value = event.playheadTime;    		
    		updateTimeText();    		
    		  		   		
  		}
  		private function onVideoComplete(event:VideoEvent):void{
  			trace("video is complete");
    		if (_debug)
    			traceVideo += "onVideoComplete\n";	 
  		}
  		private function onVideoClose(event:VideoEvent):void{
  			trace("video was closed");
    		if (_debug)
    			traceVideo += "onVideoClose\n";	
  		}
  		
  		// only used for progressive download
  		private function onProgress(event:ProgressEvent):void{
  			//trace("onProgress - bytesLoaded: " + bytesLoaded + " and bufferTime: " + bufferTime); 
    		if (_debug)
    			traceVideo += "onProgress - bytesLoaded: " + event.bytesLoaded + "\n"; 			
  		}
  		private function onVideoReady(event:VideoEvent):void{
    		if (_debug)
    			traceVideo += "onVideoReady\n"; 
    			
    		// for some reason Adobe thinks it's fun to break your app with uint errors
			// such as when an invalid server is loaded, therefore, catch those suckers here			
			trace("onVideoReady and state is: " + state + " and stateResponsive is: " + stateResponsive);
		}
    	private function onMetadataReceived(event:MetadataEvent):void{
    		trace("got metadata : " + ObjectUtil.toString(event.info));    		
    		if (_debug)
    			traceVideo += "onMetadataReceived\n " + ObjectUtil.toString(event.info) + "\n";
    		
    		_videoControlBar.scrubberSlider.totalTime = totalTime;
    		updateTimeText();
    		    		
			if (_customAutoPlay){
				trace("custom autoplay: " + _customAutoPlay);
				tryPlay();
			}	
    			
			trace("onMetadataReceived totalTime: " + totalTime);    			
    	}
    	private function onCuePoint(event:CuePointEvent):void{
    		trace("got cuepoint: " + event.cuePointTime);
    		if (_debug)
    			traceVideo += "onCuePoint\n";	
    	}
    	    	    	
    	
    	
    	
    	
    	// LOCAL VARIABLES
		[Bindable] public var traceVideo:String = '';
		public var rtmpBufferTime:int = 3;
		public var progressivBufferTime:int = 5;
		private var _numFormatter:NumberFormatter = new NumberFormatter();
		private var _dateFormatter:DateFormatter = new DateFormatter();
		private var _timerFontSize:Number;
		private var _videoControlBar:VideoControlBar;
		private var _statusLabel:Label;
    	private var _playerX:Number;
    	private var _playerY:Number;
    	private var _playerHasLoaded:Boolean;		
		private var _smoothing:Boolean;
        private var _smoothingChanged:Boolean;
        
        // GETTERS AND SETTERS
		public function get smoothing():Boolean
		{
			return _smoothing;
		}		
		public function set smoothing(value:Boolean):void
		{
            if (_smoothing != value)
            {
                _smoothing = value;
                _smoothingChanged = true;
                invalidateProperties();
                dispatchEvent(new Event("smoothingChanged"));
            }
        }    	
    			
		private function fullScreenHandler(evt:FullScreenEvent):void {
            if (evt.fullScreen) {
                /* Switch to full screen mode. */
                this.x = 0;
                this.y = 0;
                this.height = systemManager.stage.height;
                _videoControlBar.width = this.width = systemManager.stage.width;
                 _fullScreen = true;                
            }else {
                /* Switch to normal mode. */
                this.x = _playerX;
                this.y = _playerY;
                this.height = _playerHeight;
                _videoControlBar.width = this.width = _playerWidth;
                _fullScreen = false;
            }
        }
                
		private var _debug:Boolean;
        public function get debug(): Boolean { return _debug; }
	  	public function set debug(value:Boolean):void {
	  		_debug = value;
	  		if (!value)
	  			traceVideo = '';
	  	}
	  	
        private var _fullScreen: Boolean;
	  	public function get fullScreen(): Boolean { return _fullScreen; }
	  	public function set fullScreen(value:Boolean):void {
	  		_fullScreen = value;
	  		try{
	  			systemManager.stage.displayState = value ? StageDisplayState.FULL_SCREEN : StageDisplayState.NORMAL;
	  		}catch(err:SecurityError){
	  			trace("full screen not allowed - see http://blog.flexexamples.com/2007/08/07/creating-full-screen-flex-applications/");
	  		}
	  	}               
        
        private var _controlBarHeight:Number = 21;
    	public function set controlBarHeight(value:Number):void{    		
    		_controlBarHeight = value;  		
    	}
    	public function get controlBarHeight():Number{
    		return _controlBarHeight;
    	}
    	
    	private var _controlBarWidth:Number;
    	public function set controlBarWidth(value:Number):void{    		
    		_controlBarWidth = value; 		
    	}
    	public function get controlBarWidth():Number{
    		return _controlBarWidth;
    	}    	
        
    	private var _playerHeight:Number;
    	public function set playerHeight(value:Number):void{    		
    		_playerHeight = value;
    		height = value;    		
    	}
    	public function get playerHeight():Number{
    		return _playerHeight;
    	}
    	
    	private var _playerWidth:Number;
    	public function set playerWidth(value:Number):void{    		
    		_playerWidth = value;
    		width = value;
    	}
    	public function get playerWidth():Number{
    		return _playerWidth;
    	}

		// VARIABLE OVERRIDES //
    	
    	// setting autoplay to true when streaming will break all over everything
    	// make sure to only allow autoplay after a stream has loaded
    	private var _customAutoPlay:Boolean = false;
		override public function get autoPlay():Boolean
		{
			return super.autoPlay;
		}
		override public function set autoPlay(value:Boolean):void
		{
			super.autoPlay = false;
			_customAutoPlay = value;
		}   
    	
    	// source is a pain because the video won't reload if the same source is loaded twice
    	// here's another nifty tidbit, you must turn autoplay off when you load a new source
    	// if autoplay was true when you loaded the player, otherwise, you'll get nothing  	
    	private var _source:String = '';    	
		override public function get source():String
		{
			return super.source;
		}
		override public function set source(value:String):void
		{			
			if (value != null){
				if (autoPlay)
					trace("WTF AUTOPLAY IS TRUE HOW IS THIS POSSIBLE??????????????????????");
					
				trace("setting source state is: " + state + " and stateResponsive is: " + stateResponsive);
				
				if (state == VideoEvent.PLAYING){
					trace("while setting source and playing was true, trying to pause");
					pause();				
				}
					
				if (value == super.source){				
					trace("trying to clear source");
					playheadTime = 0;
					tryPlay();					
				}else{
					bufferTime = value.indexOf("rtmp://") != -1 ? rtmpBufferTime : progressivBufferTime;
					super.source = value;		
				}
				
				// in order to catch invalid servers you may need to call a load or something here
				// but then other things will break so meh				
			}
		}
		
		
		// UTILITY FUNCTIONS
		
		// don't you love how you have to try/catch things in order to capture flash player errors
    	public function tryLoad():void{
    		try{
				load();
				_playerHasLoaded = true;
			}catch(err:VideoError){ _playerHasLoaded = false; onStateChange(new VideoEvent(VideoEvent.STATE_CHANGE,false,false,VideoEvent.CONNECTION_ERROR)) }
    	}
    	public function tryPlay():void{
    		try{
				play();	
				_playerHasLoaded = true;
			}catch(err:VideoError){ _playerHasLoaded = false; onStateChange(new VideoEvent(VideoEvent.STATE_CHANGE,false,false,VideoEvent.CONNECTION_ERROR)) }
    	}
		
		// supposed to clear the video display ( may have bugs )
		public function clear():void {
            if (state == VideoEvent.PLAYING)
            	pause();
            
            if (mx_internal::videoPlayer)
            {	
            	mx_internal::videoPlayer.clear();
            }
        }
        public function smoothVideo():void {
        	if (mx_internal::videoPlayer)
            {
               	_smoothingChanged = false;
            	mx_internal::videoPlayer.smoothing = _smoothing;
            }
        }


		private function updateTimeText():void{
			/* If playhead time is 0, set to 100ms so the DateFormatter doesnt return an empty string. */
	     	var pT:Number = playheadTime || 0.1;
            var tT:Number = totalTime;

			var tzOffset:Date = new Date();
			// This takes it one day in future minus the time zone offset
			var tzDiff:Number = (86400 - tzOffset.getTimezoneOffset()*60) *1000;
			
			var pTimeMS:Date = new Date(pT * 1000 + tzDiff);
			var tTimeMS:Date = new Date(tT * 1000 + tzDiff);

            _videoControlBar.timerLabel.text = formatTime(pTimeMS) + " / " + formatTime(tTimeMS);            
		}

		private function formatTime(item:Date):String {
       		return _dateFormatter.format(item);
        }

	}
}